#!/usr/bin/env python
# Axe GUI Library (InfinGUI?) Builder v1.0
# (C) Copyright 2011 Albert Huang.
# 

import tokenizer
import os, sys
import getopt
import webbrowser
import subprocess
import datetime
from cStringIO import StringIO

# Version variable
VERSION="1.0"

# Core variables

# Debugging?
global DEBUG
DEBUG=0

# Where to get help?
global webhelpurl
webhelpurl='http://www.omnimaga.org/index.php?board=146.0'

# Builder actions
global actions
actions=[]

# What to build?
global tobuild
tobuild=[]

# SPASM path
global spasmpath
spasmpath=""

# What files need to be tokenized?
# Format: [ 'source file', 'target 8xp file' ]
# Separate each spec array with a comma.
TOTOKEN = [
			['AXEGUI.txt', 'AXEGUI.8xp'],
			['GUIDEMO.txt', 'GUIDEMO.8xp']
			]

# What files need to be assembled?
# Format: [ 'Z80 source file', 'target 8xp file' ]
# Separate each spec array with a comma.
TOASSEM = [
			['DCSAxiom.z80', 'DCSAxiom.8xp']
			]

##################################################
# Core functions
##################################################
# Hexidecimal to decimal converter
def hex2dec(s):
	return int(str(s), 16)

# Find element in array
def findelement(string, thelist):
	"""Return first item in sequence where f(item) == True."""
	dprint("[DEBUG] [FindElement] Search item is "+string)
	dprint("[DEBUG] [FindElement] List to look inside of is "+str(thelist))
	for item in thelist:
		dprint("[DEBUG] [FindElement] String is "+string+", item is "+item)
		if string == item: 
			return True
			break
	return False

##################################################
# CLI functions
##################################################

# Help/usage
def help_usage():
	print "Axe GUI Builder v"+VERSION+""
	bar=""
	for i in range(0,len("Axe GUI Builder v"+VERSION+"")):
		bar=bar+"="
	print bar
	print "Usage: "+sys.argv[0]+" [option(s)]\n"
	print "General options:"
	print "\t"+"-b, --build"+"\t\t"+"Build Axe GUI. (Default) This implies -n"
	print "\t\t\t\t"+"(building regular Axe GUI source code) and -s (building Axe"
	print "\t\t\t\t"+"GUI DCS edition/wrapper)."
	print "\t"+"-r, --reverse"+"\t\t"+"Detokenize Axe GUI source to raw text."
	print "\t\t\t\t"+"Useful for recovering the raw text source of Axe GUI."
	print "\t"+"-c, --clean"+"\t\t"+"Remove any built files."
	print "\t"+"-t, --test"+"\t\t"+"Test the built files - send them to WabbitEmu."
	print "Building choice options:"
	print "\t"+"-n, --normalbuild"+"\t"+"Build regular Axe GUI source code (the"
	print "\t\t\t\t"+"'original' Axe GUI)."
	print "\t"+"-s, --dcsbuild"+"\t\t"+"Build Axe GUI DCS edition/wrapper."
	print "Building options:"
	print "\t"+"-p, --spasmpath=[path]"+"\t"+"Specify path to the SPASM assembler."
	print "\t\t\t\t"+"This is usually automatically detected by the builder."
	print "\t"+"-d, --debug"+"\t"+"Enable builder debugging."
	print "Help/About:"
	print "\t"+"-h, --help"+"\t\t"+"Get help! :) (Shows this help)"
	print "\t"+"-w, --webhelp"+"\t\t"+"Go to PRIZM hacking site for more info/help! :)"
	#print "\t"+"-c, --credits"+"\t\t"+"Who made this awesome program? :D"
	print "\t\t\t\t"+"(This also shows the license of this program.)"
	print ""
	print "You may specify multiple commands to the builder, and the builder"
	print "will execute each based on the order of the commands (arguments)."

# Credits/license
def credits_license():
	print "Axe GUI Builder v"+VERSION+""
	bar=""
	for i in range(0,len("Axe GUI Builder v"+VERSION+"")):
		bar=bar+"="
	print bar
	print "(C) Copyright 2011 Albert Huang."
	print ""
	print "This program is under.... [insert license here, GPL perhaps?]"
	
# Pretty printing?
def tprint(str="\n"):
	if str=="\n":
		print ""
	else:
		print " == "+str

# Debug printing - only prints if debugging is enabled, either from the
# commandline or in the script.
def dprint(str="\n"):
	global DEBUG
	if DEBUG==1:
		if str=="\n":
			print ""
		else:
			print " ++ "+str

# Web help function
def LaunchWebHelp():
	tprint("Opening online help...")
	webbrowser.open(webhelpurl)
	tprint("Online help launched. If nothing happens after a few")
	tprint("moments, please open the following URL:")
	tprint(webhelpurl)

# Builder functions
def doclean():
	tprint("Cleaning source tree...")
	for file in [ "src/AXEGUI_READY.txt", "src/AXEGUI.8xp", "src/dcs7axiom/DCSAxiom.8xp" ]:
		try:
			os.remove(file)
			tprint("Removed file "+file+"!")
		except:
			tprint("The file "+file+" doesn't exist, so skipping.")

def dobuild():
	tprint("Building...")
	for build in tobuild:
		if build == 'normal':
			tprint("\tBuilding Axe GUI...")
			for src in TOTOKEN:
				fileexist = 0
				try:
					open(os.path.join("src", "axegui", src[1]), "r").close()
					fileexist = 1
				except:
					fileexist = 0
				if fileexist == 1:
					tprint("\t\tSkipping "+src[0]+", since it's already built ("+src[1]+" already exists)!")
				else:
					# Create the header!
					tprint("\t\tAdding header to "+src[0]+"...")
					# Read the original source file
					origsrcf = open(os.path.join("src", "axegui", src[0]), "r")
					origsrc = origsrcf.read()
					origsrcf.close()
					
					# Check name, and pad with null characters if necessary (\x00)
					# or trim it down.
					name = src[0].split('.')[0].upper()
					if len(name) < 8:
						remain = 8 - len(name)
						for n in range(0, remain):
							name = name + "\x00"
					if len(name) > 8:
						name = name[:8]
					
					# Create comment!
					# The length of the comment is 42, padded by null bytes if necessary.
					# Comment is "Prgm by AGLBldr v1.0 on xx/xx/xx xx:xx:xx"
					# Comment is "Single file dated Fri Mar 19 16:27:39 20  "
					datestr = datetime.datetime.now().strftime("%m/%d/%y %H:%M:%S")
					comment = "Prgm by AGLBldr v1.0 on "+datestr
					
					if len(comment) < 42:
						remain = 42 - len(comment)
						for n in range(0, remain):
							comment = comment + "\x00"
					if len(name) > 42:
						comment = comment[:42]
					
					# Is the program protected?
					protectedfield = "not protected"
					
					# Put it all together! :D
					finalsrc = name + "\n" + protectedfield + "\n" + comment + "\n" + origsrc
					
					newname = src[0].replace('.txt', '_READY.txt')
					tprint("\t\tFormatting "+src[0]+" to "+newname+"...")
					ftmp = open(os.path.join("src", "axegui", newname), "w")
					ftmp.write(finalsrc)
					ftmp.close()
					
					tprint("\t\tTokenizing "+src[0]+" (using formatted "+newname+")...")
					savedstdout = sys.stdout
					sys.stdout = compileout = StringIO()
					ret = tokenizer.recompile(os.path.join("src", "axegui", newname), os.path.join("src", "axegui", src[1]))
					sys.stdout = savedstdout
					print("\t\t\t"+compileout.getvalue().strip())
					if ret:
						tprint("\t\tTokenization successful! :D")
					else:
						print "Tokenization failed! :( See the above error messages for details."
						sys.exit(1)
		if build == 'dcs':
			tprint("\tBuilding Axe GUI, DCS edition/wrapper...")
			for src in TOASSEM:
				fileexist = 0
				try:
					open(os.path.join("src", "dcs7axiom", src[1]), "r").close()
					fileexist = 1
				except:
					fileexist = 0
				if fileexist == 1:
					tprint("\t\tSkipping "+src[0]+", since it's already built ("+src[1]+" already exists)!")
				else:
					tprint("\t\tAssembling "+src[0]+"...")
					olddir=os.getcwd()
					os.chdir(os.path.join("src", "dcs7axiom"))
					ASM_PROC = subprocess.Popen([spasmpath, '-N', src[0], src[1]], shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
					ASM_OUTPUT = ASM_PROC.stdout.read()
					ASM_ERRCODE = ASM_PROC.wait()
					os.chdir(olddir)
					for line in ASM_OUTPUT.strip().split("\n"):
						tprint("\t\t\t"+line)
					if (ASM_ERRCODE != 0) and (ASM_ERRCODE != 1):
						print "SPASM returned error code: "+str(ASM_ERRCODE)
						print "Assembling failed! :( See the above error messages for details."
						sys.exit(1)

def doreverse():
	tprint("Detokenizing files...")
	for src in TOTOKEN:
		fileexist = 0
		try:
			open(os.path.join("src", src[1]), "r").close()
			fileexist = 1
		except:
			fileexist = 0
		if fileexist == 1:
			tprint("\t\tDetokenizing "+src[1]+"...")
			savedstdout = sys.stdout
			sys.stdout = decompileout = StringIO()
			ret = tokenizer.decompile(os.path.join("src", src[1]), os.path.join("src", "rev_"+src[0]))
			sys.stdout = savedstdout
			print("\t\t\t"+decompileout.getvalue().strip())
			if ret:
				tprint("\t\tDetokenization successful! :D")
			else:
				print "Detokenization failed! :( See the above error messages for details."
				sys.exit(1)
		else:
			tprint("\t\tCouldn't find "+src[1]+", so not detokenizing...")

# The main function!
def main(argv=None):
	global actions, DEBUG, tobuild, spasmpath
	if argv is None:
		argv = sys.argv
	
	args = []
	
	try:
		# NOTE TO SELF: for short args, ":" will require argument,
		# "=" in long args to require an argument
		opts, args = getopt.gnu_getopt(argv[1:], "brctnsp:dhwl", ["build", "reverse", "clean", "test", "normalbuild", "dcsbuild", "spasmpath", "debug", "help", "webhelp", "credits"])
	except getopt.error, err:
		print "ERROR: "+str(err.msg)+"\n"
		help_usage()
		sys.exit(2)
	
	for opt, arg in opts:
		# Is the user asking for help?
		if opt in ("-h", "--help"):
			help_usage()
			sys.exit()
		# ...or do they like web help?
		if opt in ("-w", "--webhelp"):
			LaunchWebHelp()
			sys.exit()
		# Maybe they want to know who wrote this program!
		if opt in ("-l", "--credits"):
			credits_license()
			sys.exit()
		if opt in ("-b", "--build"):
			if (not(findelement("normal", tobuild))):
				tobuild.append("normal")
			if(not(findelement("dcs", tobuild))):
				tobuild.append("dcs")
			actions.append("build")
		if opt in ("-n", "--normalbuild"):
			if (not(findelement("normal", tobuild))):
				tobuild.append("normal")
			if(not(findelement("build", actions))):
				actions.append("build")
		if opt in ("-s", "--dcsbuild"):
			if(not(findelement("dcs", tobuild))):
				tobuild.append("dcs")
			if(not(findelement("build", actions))):
				actions.append("build")
		# Other tasks
		# Detokenize the compiled file?
		if opt in ("-r", "--reverse"):
			actions.append("reverse")
		# Clean the source tree?
		if opt in ("-c", "--clean"):
			actions.append("clean")
		# Grab assembler path, if specified
		if opt in ("-p", "--spasmpath"):
			spasmpath = arg
		# Does the user want to debug?
		if opt in ("-d", "--debug"):
			global DEBUG
			DEBUG=1
	if not len(sys.argv)>1:
			print "ERROR: No arguments specified!"
			help_usage()
			sys.exit(1)
	
	tprint("Doing sanity checks...")
	
	# Check for SPASM and a valid SPASM path.
	if spasmpath != '':
		tprint("Checking if SPASM path is correct...")
		try:
			open(spasmpath, "r").close()
		except:
			print "ERROR: The SPASM assembler path you have specified is invalid!"
			print "Check to make sure that the program exists, there are no"
			print "permission problems, and that the program is accessible, and"
			print "then try again."
			sys.exit(1)
	else:
		paths=os.environ["PATH"].split(":")
		found=[]
		finalpick=""
		tprint("Searching for spasm in PATH...")
		for path in paths:
			try:
				if sys.platform == 'win32':
					open(os.path.join(path, "spasm.exe")).close()
					found.append(os.path.join(path, "spasm.exe"))
				else:
					open(os.path.join(path, "spasm")).close()
					found.append(os.path.join(path, "spasm"))
			except:
				# Do nothing
				continue
		if len(found) == 0:
			print "ERROR: The SPASM assembler couldn't be found!"
			print "Check to make sure that the program exists, there are no"
			print "permission problems, and that the program is accessible, and"
			print "then try again. It's likely that you will need to install"
			print "SPASM assembler before you can run this again."
			sys.exit(1)
		if len(found) > 1:
			print "Multiple SPASM assemblers have been found!"
			n=1
			for pick in found:
				print str(n)+": "+pick
				n=n+1
			print "Enter the number of the SPASM assembler you want to use, then"
			print "press ENTER."
			choice = raw_input("Choose: ")
			if 1 < int(choice) < (len(found)):
				spasmpath = found[int(choice)-1]
		else:
			spasmpath = found[0]
	tprint("Using SPASM assembler found in "+spasmpath+"...")
	
	if len(actions) > 1:
		tprint("The builder will:")
		for n in range(0, len(actions)):
			if actions[n] == 'clean':
				desc = "clean up source tree"
			if actions[n] == 'build':
				desc = "build the source code"
			if actions[n] == 'reverse':
				desc = "detokenize files into source code"
			if n == (len(actions) - 1):
				tprint("\t"+"and "+desc+"!")
			else:
				tprint("\t"+desc+",")
		dprint("Friendly print debug: Actions array is "+str(actions))
		dprint("Friendly print debug: for loop range array is "+str(range(0, len(actions))))
	
	for act in actions:
		if act == 'clean':
			doclean()
		if act == 'build':
			dobuild()
		if act == 'reverse':
			doreverse()
if __name__ == "__main__":
	sys.exit(main())

